package org.anddev.andengine.extension.tbsengine.client;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.engine.camera.SmoothCamera;
import org.anddev.andengine.engine.options.EngineOptions;
import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.anddev.andengine.entity.layer.tiled.tmx.util.exception.TMXLoadException;
import org.anddev.andengine.entity.scene.Scene;
import org.anddev.andengine.entity.util.FPSCounter;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.IMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.server.IServerMessage;
import org.anddev.andengine.extension.multiplayer.protocol.client.IServerMessageHandler;
import org.anddev.andengine.extension.multiplayer.protocol.client.connector.ServerConnector;
import org.anddev.andengine.extension.multiplayer.protocol.shared.Connection;
import org.anddev.andengine.extension.multiplayer.protocol.util.MessagePool;
import org.anddev.andengine.extension.tbsengine.client.ClientConnectionGateway.IOnGameClientConnectionStatusListener;
import org.anddev.andengine.extension.tbsengine.match.AbsClientMatch;
import org.anddev.andengine.extension.tbsengine.message.client.ClientChatmessageMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientJoinOrLeaveServerRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientMatchJoinLeaveRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.DefaultTBSclientMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.server.AbsServerMatchMessage;
import org.anddev.andengine.extension.tbsengine.message.server.DefaultTBSserverMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.server.ServerChatMessageForwardMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerJoinOrLeaveServerReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerMatchPlayerJoinedLeftAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ingame.AbsServerMatchReconnectRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.player.BaseHumanPlayer;
import org.anddev.andengine.extension.tbsengine.shared.MatchJoinOrPreparationType;
import org.anddev.andengine.extension.tbsengine.shared.NoMatchPreparationTypeProvidedException;
import org.anddev.andengine.extension.tbsengine.shared.TBSengineConstants;
import org.anddev.andengine.ui.activity.BaseGameActivity;
import org.xml.sax.SAXException;

import android.content.Intent;
import android.os.Bundle;

/**
 * This is an abstract implementation of an InGame Activity. It will represent
 * the match itself and all interaction abilities on the client side of the game.
 * 
 * @author Tobias Boehm
 *
 * @param <ClientMatchClass> - Your implementation of {@link AbsClientMatch}
 * @param <ServerMatchReconnectRequestReplyMessageClass> - Your Implementation of {@link AbsServerMatchReconnectRequestReplyMessage}
 */
public abstract class AbsInGameActivity<ClientMatchClass extends AbsClientMatch, ServerMatchReconnectRequestReplyMessageClass extends AbsServerMatchReconnectRequestReplyMessage> extends BaseGameActivity implements IOnGameClientConnectionStatusListener, DefaultTBSclientMessageFlags, DefaultTBSserverMessageFlags, TBSengineConstants {
  
	// Basic Camera Size
	protected float mCameraWidth;
	protected float mCameraHeight;
     
	// GUI and View
    protected SmoothCamera mCamera;
    //protected Font mFont;

    // Client <-> Server Communication Components
    protected ClientConnectionGateway mClientConnectionGateway;
    protected Map<Short,Class<? extends IServerMessage>> mFlagsServerMessageClassesMap;
	protected Map<Short,IServerMessageHandler> mFlagsServerMessageHandlersMap;	
    protected MessagePool<IMessage> mClientMessagePool;
    
    // Client Components
    protected ClientMatchClass mClientMatch;
    protected BaseHumanPlayer mMainLocalPlayer;
		
	// Other Components
    protected MatchJoinOrPreparationType mMatchPreparationType;
    
	
	// Abstract Methods for initialization
	protected abstract BaseHumanPlayer loadMainLocalPlayer();
	
	/**
	 * You have to add at least the class file for the
	 * {@link ServerMatchReconnectRequestReplyMessageClass}.
	 * 
	 * @return
	 */
	protected abstract Map<Short,Class<? extends IServerMessage>> addAdditionalFlagsServerMessageClasses();

	/**
	 * You have to add at least a handler for the {@link ServerMatchReconnectRequestReplyMessageClass},
	 * which calls the method {@link onServerMatchJoinRequestReply()}.
	 * @return
	 */
	protected abstract Map<Short,IServerMessageHandler> addAdditionalFlagsServerMessageHandlers();
	
	protected abstract void registerAdditionalClientMessages(final MessagePool<IMessage> pClientMessagePool);
		
	protected abstract ClientConnectionGateway createClientConnectionGateway(final MatchJoinOrPreparationType pMatchPreparationType, final Intent pIntent,final Map<Short, Class<? extends IServerMessage>> pFlagsServerMessageClassesMap, final Map<Short, IServerMessageHandler> pFlagsServerMessageHandlersMap) throws NumberFormatException, IOException;

    protected abstract ClientMatchClass createClientMatch(final MatchJoinOrPreparationType pMatchPreparationType, final ServerMatchReconnectRequestReplyMessageClass pMessage) throws TMXLoadException, IOException, SAXException;

    protected abstract Scene createInGameScene(final ClientMatchClass pClientMatch);
	
	
	// Abstract Methods for Message Handling
	protected abstract void onServerChatMessageForward(final ServerChatMessageForwardMessage pMessage);

	protected abstract void onServerMatchMessage(final AbsServerMatchMessage pMessage);

	@Override
	protected void onDestroy() {

		if((this.mClientConnectionGateway != null) && this.mClientConnectionGateway.isConnected()){
			
			final ClientJoinOrLeaveServerRequestMessage message = (ClientJoinOrLeaveServerRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST);
			
			message.setJoinServer(false);
			message.setPlayer(this.mMainLocalPlayer);
			
			try {
				this.mClientConnectionGateway.sendClientMessage(message);
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				
				this.mClientMessagePool.recycleMessage(message);
			}
		}
		
		super.onDestroy();	
	}
	
	
    @Override
    protected void onCreate(Bundle pSavedInstanceState) {
    
    	// Get the MatchPreparationType
		try {
						
			this.mMatchPreparationType = MatchJoinOrPreparationType.valueOf(getIntent().getStringExtra(TBSE_MATCH_JOIN_OR_PREPARATION_TYPE_INTENT_EXTRA));
			
		}catch(Exception e){
			
			throw new NoMatchPreparationTypeProvidedException();
		}		
		
		// Calculate Camera Size
		this.mCameraWidth = this.getResources().getDisplayMetrics().widthPixels;
		this.mCameraHeight = this.getResources().getDisplayMetrics().heightPixels;
		
		// Load main local Player
		this.mMainLocalPlayer = this.loadMainLocalPlayer();
					
		
		// Init ClientMessagePool
		this.mClientMessagePool = this.initClientMessagePool();
		this.registerAdditionalClientMessages(this.mClientMessagePool);
		
		// Init Handlers
		this.initServerMessagesAndHandlers();
		
		// Create and connect Game client
		try {
			this.mClientConnectionGateway = this.createClientConnectionGateway(this.mMatchPreparationType, getIntent(), this.mFlagsServerMessageClassesMap, this.mFlagsServerMessageHandlersMap);
			this.mClientConnectionGateway.asyncConnect();
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		    	
    	super.onCreate(pSavedInstanceState);
    }
    
    protected void onServerReconnectReply(final ServerMatchReconnectRequestReplyMessageClass pMessage) {
		
		if(pMessage.isAccepted()){
				
    		try {
				this.mClientMatch = this.createClientMatch(this.mMatchPreparationType, pMessage);
				
				this.mEngine.onLoadComplete(this.createInGameScene(this.mClientMatch));
				
			} catch (TMXLoadException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}						
		}
	}
    
    @Override
    public Engine onLoadEngine() {
          
    		// Creating Camera and Engine
    		this.mCamera = new SmoothCamera(0, 0, mCameraWidth, mCameraHeight,1000,1000,1);
            
    		final EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(mCameraWidth, mCameraHeight), this.mCamera);
                		    		
            return new Engine(engineOptions);
    }
 
    @Override
    public void onLoadResources() {
 
    }
 
	@Override
    public Scene onLoadScene() {
            
    		// Counting FPS
    		this.getEngine().registerUpdateHandler(new FPSCounter());
 
    		// Create dummy scene. The real scene gets created @onServerMatchJoinRequestReply
    		final Scene dummyScene = new Scene(1);
    		    		 
            return dummyScene;
    }
 
	@Override
    public void onLoadComplete() {
 
    }
        
    private MessagePool<IMessage> initClientMessagePool() {
		
		final MessagePool<IMessage> messagePool = new MessagePool<IMessage>();
		
		messagePool.registerMessage(FLAG_CLIENT_CHATMESSAGE, ClientChatmessageMessage.class);
		messagePool.registerMessage(FLAG_CLIENT_MATCH_JOIN_LEAVE_REQUST, ClientMatchJoinLeaveRequestMessage.class);
		messagePool.registerMessage(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST, ClientJoinOrLeaveServerRequestMessage.class);
		
		return messagePool;
	}
	
	private void initServerMessagesAndHandlers() {

		this.mFlagsServerMessageClassesMap = this.createFlagsServerMessageClassesMap();
		
		final Map<Short, Class<? extends IServerMessage>> additionalFlagsServerMessageClasses = this.addAdditionalFlagsServerMessageClasses();
		if(additionalFlagsServerMessageClasses != null){
		
			this.mFlagsServerMessageClassesMap.putAll(additionalFlagsServerMessageClasses);
		}
		
		this.mFlagsServerMessageHandlersMap = this.createFlagsServerMessageHandlersMap();
		
		final Map<Short, IServerMessageHandler> additionalFlagServerMessageHandlers = this.addAdditionalFlagsServerMessageHandlers();
		if(additionalFlagServerMessageHandlers != null){
			
			this.mFlagsServerMessageHandlersMap.putAll(additionalFlagServerMessageHandlers);
		}
	}
	
	private Map<Short, Class<? extends IServerMessage>> createFlagsServerMessageClassesMap() {
		
		final HashMap<Short, Class<? extends IServerMessage>> map = new HashMap<Short, Class<? extends IServerMessage>>();
		
		map.put(FLAG_SERVER_CHAT_MESSAGE_FORWARD, ServerChatMessageForwardMessage.class);
		map.put(FLAG_SERVER_MATCH_PLAYER_JOINED_LEFT_ANNOUNCEMENT, ServerMatchPlayerJoinedLeftAnnouncementMessage.class);
		map.put(FLAG_SERVER_JOIN_OR_LEAVE_SERVER_REPLY, ServerJoinOrLeaveServerReplyMessage.class);
	
		return map;
	}
	
	private Map<Short, IServerMessageHandler> createFlagsServerMessageHandlersMap() {
		
		final HashMap<Short, IServerMessageHandler> map = new HashMap<Short, IServerMessageHandler>();
				
		map.put(FLAG_SERVER_CHAT_MESSAGE_FORWARD, new AbsServerMessageCopyHandler<ServerChatMessageForwardMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerChatMessageForwardMessage pMessage) {
				
				onServerChatMessageForward(pMessage);
			}
		});
				
		map.put(FLAG_SERVER_MATCH_PLAYER_JOINED_LEFT_ANNOUNCEMENT, new AbsServerMessageCopyHandler<ServerMatchPlayerJoinedLeftAnnouncementMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerMatchPlayerJoinedLeftAnnouncementMessage pMessage) {
				
				onServerMatchMessage(pMessage);
			}
		});
				
				
		map.put(FLAG_SERVER_JOIN_OR_LEAVE_SERVER_REPLY, new AbsServerMessageCopyHandler<ServerJoinOrLeaveServerReplyMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerJoinOrLeaveServerReplyMessage pMessage) {
				
				onJoinOrLeaveServerReply(pMessage);
			}
		});
				
		return map;
	}
        
    public Engine getEngine(){
    	
    	return this.mEngine;
    }

	public SmoothCamera getCamera() {
		return mCamera;
	}

	@Override
	public void onGameServerConnected() {
		
		final ClientJoinOrLeaveServerRequestMessage message = (ClientJoinOrLeaveServerRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST);
		
		message.setPlayer(this.mMainLocalPlayer);
		message.setJoinServer(true);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	private void onJoinOrLeaveServerReply(final ServerJoinOrLeaveServerReplyMessage pMessage) {
		
		if(pMessage.isAccepted()){
			
			final ClientMatchJoinLeaveRequestMessage message = (ClientMatchJoinLeaveRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_MATCH_JOIN_LEAVE_REQUST);
			
			message.setPlayer(this.mMainLocalPlayer);
			message.setMatchID(UUID.fromString(this.getIntent().getExtras().getString(TBSE_MATCH_ID_INTENT_EXTRA)));
			message.setJoinMatch(true);
			
			try {
				this.mClientConnectionGateway.sendClientMessage(message);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally{
				this.mClientMessagePool.recycleMessage(message);
			}
			
		}else{
			
			//TODO: Fehlermeldung überlegen
		}
	}

	@Override
	public void onGameServerDisconnected() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onSocketGameServerNotReachable(String pDestinationIP,	int pDestinationPort) {
		// TODO Auto-generated method stub
		
	}
}